package org.opens.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.opens.netty.initializer.TcpClientChannelInitializer;

import java.util.Objects;
import java.util.Scanner;

/**
 * 功能描述
 *
 * @author: zhangyuming
 * @date: 2023/12/6 10:52
 */
@Getter
@Slf4j
public class TcpClient {

    /**
     * 远程ip
     */
    private final String host;

    /**
     * 远程端口
     */
    private final int port;

    /**
     * 创建的连接channel
     */
    private Channel channel;

    private final ChannelInitializer<Channel> channelInitializer;

    public TcpClient(String host, int port, ChannelInitializer<Channel> channelInitializer) {
        this.host = host;
        this.port = port;
        this.channelInitializer = channelInitializer;
    }

    /**
     * 启动TCP_CLIENT客户端
     */
    public synchronized void start() {
        // double check
        if (this.channel != null) {
            return;
        }
        // 创建工作线程组
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            // 开始创建连接
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    /*
                    ，用于设置客户端Channel的类型1. 在上面提供的示例代码中，我们使用NioSocketChannel.class来创建一个基于NIO的客户端Channel.
                     */
                    .channel(NioSocketChannel.class)
                    /*
                    当设置为true时，TCP会实现监控连接是否有效，当连接处于空闲状态的时候，超过了2个小时，本地的TCP实现会发送一个数据包给远程的
                    socket，如果远程没有发回响应，TCP会持续尝试11分钟，直到响应为止，如果在12分钟的时候还没响应，TCP尝试关闭socket连接.
                     */
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    /*
                    用于禁用Nagle算法1. Nagle算法是将小的数据包组装为更大的帧然后进行发送，而不是输入一次发送一次，因此在数据包不足的时候会等
                    待其他数据的到来，组装成大的数据包进行发送，虽然该算法有效提高了网络的有效负载，但是却造成了延时。当您设置ChannelOption.
                    TCP_NODELAY为true时，Nagle算法将被禁用，这对于需要即时传输小数据的场景非常有用.
                     */
                    .option(ChannelOption.TCP_NODELAY, true);

            // 添加处理器
            if (this.channelInitializer != null) {
                bootstrap.handler(this.channelInitializer);
            }
            // 用于连接到远程服务器并等待连接完成; sync表示一个同步方法
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            // 上一步是一个同步操作, 到这里必然能获取连接channel
            channel = channelFuture.channel();
            channel.closeFuture().addListener(new GenericFutureListener() {
                @Override
                public void operationComplete(Future future) throws Exception {
                    log.info("TCP_CLIENT {} 已关闭.", channel);
                    // 释放线程组
                    group.shutdownGracefully();
                    // 清空channel引用
                    channel = null;
                }
            });
        } catch (Exception e){
            log.error("TCP_CLIENT({}:{})启动失败!", host, port);
            // 启动异常时需要释放线程组
            group.shutdownGracefully();
            throw new RuntimeException(e);
        }
        log.error("TCP_CLIENT({}:{})启动成功!", host, port);
    }

    /**
     * 关闭channel并释放资源
     */
    public synchronized void close() {
        if (this.channel == null) {
            return;
        }
        log.info("TCP_CLIENT {} 开始关闭.", channel);
        this.channel.close();
    }

    public static void main(String[] args) throws InterruptedException {
        TcpClient tcpClient = new TcpClient("localhost", 10086, new TcpClientChannelInitializer<SocketChannel>());
        tcpClient.start();

        Scanner input = new Scanner(System.in);
        while (input.hasNext()) {
            String value = input.nextLine();
            System.out.println("你输入了:"+ value);
            if (Objects.equals("close", value)) {
                tcpClient.close();
                break;
            }
        }
        input.close();
    }

}
